#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2024/12/11 0:48
# @Author  : 唐松
# @File    : yaml_analysis.py
# @Software: PyCharm

import allure
from util_tools.assertion_util import AssertionsUtils
from util_tools.handle_data.yaml_handler import read_testcase_file
from util_tools.logger_util import logs
from util_tools.other_util.allure_type import allure_attach_dict_result
from util_tools.requests_util import RequestsUtil
from util_tools.yaml_process.yaml_extract import YamlExtract
from util_tools.yaml_process.yaml_replace import YamlReplace


class YamlAnalysis:

    # gittest

    def __init__(self):
        self.replace = YamlReplace()
        self.send_request = RequestsUtil()
        self.extract = YamlExtract()
        self.asserts = AssertionsUtils()

    def yaml_analysis_execution(self, caseinfo):
        """
        解析并执行yaml文件中的测试用例
        Args:
         caseinfo (dict): 包含测试用例信息的yaml字典, 必须包含name、base_url、request和validation等关键字
        returns:
        """

        # print(caseinfo)

        try:
            caseinfo_keys = dict(caseinfo).keys()
            # 校验yaml文件的一级关键字是否包含name、base_url、request、validation
            if 'name' in caseinfo_keys and 'base_url' in caseinfo_keys and 'request' in caseinfo_keys and 'validation' in caseinfo_keys:
                # 获取request字典的所有键
                request_keys = dict(caseinfo['request']).keys()
                # print(f'四个一级关键字: {caseinfo_keys}')
                # 校验二级关键字是否包含method、path
                if 'method' in request_keys and 'path' in request_keys:
                    # print(f'两个二级关键字: {request_keys}')
                    name = caseinfo['name']

                    # 替换base_url中的变量
                    base_url = self.replace.parse_replace(caseinfo['base_url'])
                    # 替换path中的变量,从request中移除path并获取其值
                    path = self.replace.parse_replace(caseinfo['request'].pop('path'))
                    # 拼接完整的请求URL
                    url = base_url + path

                    # 从request中移除method并获取其值
                    method = caseinfo['request'].pop('method')

                    # request下存在headers, 将其做解析并替换, 然后headers去除
                    headers = caseinfo['request'].get('headers', None)
                    if headers is not None:
                        # 替换headers中的变量
                        headers = eval(self.replace.parse_replace(headers)) if isinstance(headers, str) else headers
                        # 更新request中的headers
                        caseinfo['request']['headers'] = headers
                        # 从request中移除headers
                        caseinfo['request'].pop('headers', None)

                    # request下存在cookies, 将其做解析并替换, 然后cookies去除
                    cookies = caseinfo['request'].get('cookies', None)
                    if cookies is not None:
                        # 替换cookies中的变量
                        cookies = eval(self.replace.parse_replace(cookies)) if isinstance(cookies, str) else cookies
                        # 更新request中的cookies
                        caseinfo['request']['cookies'] = cookies
                        # 从request中移除cookies
                        caseinfo['request'].pop('cookies', None)

                    # request下存在files, 将其做解析并替换, 然后files去除
                    files = caseinfo['request'].get('files', None)
                    if files is not None:
                        # 打开文件并更新request中的files
                        files = {key: open(value, 'rb') for key, value in files.items()}
                        caseinfo['request']['files'] = files
                        # 从request中移除files
                        caseinfo['request'].pop('files', None)

                    # 处理request中去除headers、cookies、files、path、method, 剩余一个请求体参数(data/json/params)参数类型和请求参数
                    param_type, request_params = None, None
                    # 遍历request中的参数类型
                    for param_type, params_params in caseinfo['request'].items():
                        if param_type in ['params', 'data', 'json']:
                            # 替换参数中的变量
                            request_params = self.replace.parse_replace(params_params)
                            # 更新request中的参数
                            caseinfo['request'][param_type] = request_params

                    # 执行请求
                    response = self.send_request.execute_request(name=name,
                                                                 method=method,
                                                                 url=url,
                                                                 headers=headers,
                                                                 cookies=cookies,
                                                                 files=files,
                                                                 **caseinfo['request'])
                    status_code, response_text = response.status_code, response.text
                    logs.info(f'接口实际返回结果: {response_text}')

                    # 在allure报告Test body显示内容
                    allure_info = {
                        '用例名称': name,
                        '基础地址': base_url,
                        '接口路径': path,
                        '请求方式': method,
                        'headers': allure_attach_dict_result(headers if headers else "无需headers"),
                        'Cookies': allure_attach_dict_result(cookies if cookies else "无需Cookies"),
                        '参数类型': param_type if param_type else "",
                        '请求参数': allure_attach_dict_result(request_params if request_params else "无需入参"),
                        '接口响应信息': allure_attach_dict_result(response.json())
                    }
                    for title, content in allure_info.items():
                        allure.attach(content, title, attachment_type=allure.attachment_type.JSON)

                    # 处理接口返回值提取
                    try:
                        # 存在extract, 将其做解析并替换
                        extract = caseinfo.get('extract', None)
                        # 如果extract变量不为空, 则表示需要提取单个数据
                        if extract is not None:
                            # 提取单个数据
                            self.extract.extract_data(extract, response_text)
                        # 存在extract_list, 将其做解析并替换
                        extract_list = caseinfo.get('extract_list', None)
                        # 如果extract_list变量不为空, 则表示需要提取数据列表
                        if extract_list is not None:
                            # 提取数据列表
                            self.extract.extract_data_list(extract_list, response_text)
                    except Exception as e:
                        logs.error(f'提取参数失败,错误信息:{e}, 请检查接口返回信息或表达式!')
                        raise

                    # 处理接口断言
                    finally:
                        # 存在validation, 将其做解析并替换
                        validation = self.replace.parse_replace(caseinfo.get('validation'))
                        # 调用assert对象的assert_result方法进行断言操作,
                        self.asserts.assert_result(validation, response.json(), status_code)
                else:
                    logs.error('测试文件中, 必须包含二级关键字: method、path')
            else:
                logs.error('测试文件中, 必须包含一级关键字: name、base_url、request、validation')
        except KeyError as ke:
            logs.error(f'缺少必要的键: {ke}')
        except Exception as e:
            logs.error(f'执行用例失败,错误信息: {e}')
            raise e


if __name__ == '__main__':
    data = read_testcase_file('testcase/wxTag/wx_login.yaml')[1]
    req = YamlAnalysis()
    req.yaml_analysis_execution(data)
